package org.funtester.performance.books.chapter11.section4;

/**
 * 雪花算法，生成分布式唯一ID
 */
public class SnowflakeWorker {

    private static final long START_TIMESTAMP = 1712972492276L; // 起始时间戳

    private long dataId; // 数据中心ID

    private long workerId;     // 机器ID

    private long sequence = 0L; // 序列号

    private static final long MAX_MACHINE_ID = 31L;// 机器ID最大值

    private static final long MAX_DATA_CENTER_ID = 31L;// 数据中心ID最大值

    private static final long SEQUENCE_BITS = 12L;// 序列号位数

    private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;// 机器ID左移位数

    private static final long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_SHIFT;// 数据中心ID左移位数

    private static final long TIMESTAMP_LEFT_SHIFT = DATA_CENTER_ID_SHIFT + DATA_CENTER_ID_SHIFT;// 时间戳左移位数

    private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);// 序列号掩码

    private long lastTimestamp = -1L;// 上次时间戳

    /**
     * 生成雪花算法对象
     *
     * @param dataId   数据中心ID
     * @param workerId 机器ID
     */
    public SnowflakeWorker(long dataId, long workerId) {
        if (dataId > MAX_DATA_CENTER_ID || dataId < 0) {
            throw new IllegalArgumentException("数据中心ID错误，不能大于" + MAX_DATA_CENTER_ID + "或小于0");
        }
        if (workerId > MAX_MACHINE_ID || workerId < 0) {
            throw new IllegalArgumentException("机器ID错误，不能大于" + MAX_MACHINE_ID + "或小于0");
        }
        this.dataId = dataId;
        this.workerId = workerId;
    }

    /**
     * 获取下一个ID
     *
     * @return
     */
    public synchronized long nextId() {
        long timestamp = System.currentTimeMillis();// 获取当前时间戳
        if (timestamp < lastTimestamp) {// 如果当前时间戳小于上次时间戳
            if (this.lastTimestamp - timestamp >= 2000L)
                throw new RuntimeException("时钟向后移动，拒绝生成ID，时间差" + (lastTimestamp - timestamp) + "毫秒");
            else timestamp = this.lastTimestamp;
        }
        if (lastTimestamp == timestamp) {// 如果当前时间戳等于上次时间戳
            sequence = (sequence + 1) & SEQUENCE_MASK;// 序列号+1
            if (sequence == 0) {// 如果序列号为0
                timestamp = nextMillis(lastTimestamp);// 获取下一个时间戳
            }
        } else {
            sequence = 0L;// 重置序列号
        }
        lastTimestamp = timestamp;// 保存当前时间戳
        long l = ((timestamp - START_TIMESTAMP) << TIMESTAMP_LEFT_SHIFT) | (dataId << DATA_CENTER_ID_SHIFT) | (workerId << WORKER_ID_SHIFT) | sequence;// 生成ID
        return l & Long.MAX_VALUE;// 返回ID
    }

    /**
     * 获取下一个时间戳
     *
     * @param lastTimestamp
     * @return
     */
    private long nextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();// 获取当前时间戳
        while (timestamp <= lastTimestamp) {// 如果当前时间戳小于等于上次时间戳
            timestamp = System.currentTimeMillis();// 重新获取当前时间戳
        }
        return timestamp;
    }

}
